using System;
using System.Collections.Generic;
using System.Linq;

namespace LambdaGp.Core
{
    public class GenomeDefinition
    {
        private bool _verified;
        private bool? _requiresParameterMutation;
        private Dictionary<int, List<Operator>> _operatorsByArity;

        public GenomeDefinition()
        {
            Operators = new List<Operator>();
            MaxDepth = 8;
            MaxDepthCreated = MaxDepth;
            MaxSize = 150;
        }

        public int MaxDepth { get; set; }
        public int MaxDepthCreated { get; set; }
        public int MaxSize { get; set; }
        public List<Operator> Operators { get; private set; }

        public bool RequiresParameterMutation
        {
            get
            {
                if (_requiresParameterMutation.HasValue == false)
                {
                    _requiresParameterMutation = Operators.Any(fnc => fnc.RequiresParameterMutation);
                }
                return _requiresParameterMutation.Value;
            }
        }

        public IEnumerable<Operator> Terminals
        {
            get
            {
                return GetOperatorsByArity(0);
            }
        }

        public IEnumerable<Operator> NonTerminals
        {
            get
            {
                return
                    Operators
                        .Where(func => func.Arity > 0);
            }
        }

        public List<Operator> GetOperatorsByArity(int arity)
        {
            /* return
                 Operators
                     .Where(func => func.Arity == arity)
                     .ToList();*/
            List<Operator> operators;
            if (_operatorsByArity.TryGetValue(arity, out operators) == false)
            {
                operators = new List<Operator>();
                _operatorsByArity.Add(arity, operators);
            }
            return operators;
        }

        public OperatorNode GenerateRandomGenome()
        {
            VerifyThatGenomeDefinitionIsComplete();
            return GenerateRandomOperatorNode(0, MaxDepthCreated);
        }

        public void PrepareInternalStorage()
        {
            _operatorsByArity = new Dictionary<int, List<Operator>>();
            foreach (Operator @operator in Operators)
            {
                List<Operator> operators;
                if (_operatorsByArity.TryGetValue(@operator.Arity, out operators) == false)
                {
                    operators = new List<Operator>();
                    _operatorsByArity.Add(@operator.Arity, operators);
                }

                operators.Add(@operator);
            }
        }

        public TOperator AddOperator<TOperator>(TOperator @operator)
            where TOperator : Operator
        {
            Operators.Add(@operator);
            PrepareInternalStorage();
            return @operator;
        }

        public void AddOperators(params Operator[] operators)
        {
            Operators.AddRange(operators);
            PrepareInternalStorage();
        }

        public void AddOperators(IEnumerable<Operator> operators)
        {
            Operators.AddRange(operators);
            PrepareInternalStorage();
        }

        public void PointMutate(OperatorNode operatorNode)
        {
            List<OperatorNode> list = operatorNode.OperatorTreeToList();
            OperatorNode nodeToMutate = list.GetRandomItem();
            List<Operator> operators = GetOperatorsByArity(nodeToMutate.Operator.Arity);
            Operator @operator = operators.GetRandomItem();
            nodeToMutate.Operator = @operator;
        }

        public void MacroMutate(OperatorNode operatorNode)
        {
            OperatorNode macroSource = GenerateRandomGenome();
            Cross(operatorNode, macroSource);
        }

        public void FunctionParameterMutate(OperatorNode operatorNode)
        {
            operatorNode.ExecuteOnEveryNodeInTree(node => node.OperatorParameterMutate());
        }

        public void Cross(OperatorNode fatherChild, OperatorNode mother)
        {
            List<OperatorNode> fatherChildList = fatherChild.OperatorTreeToList();
            List<OperatorNode> motherList = mother.OperatorTreeToList();

            int attempts = 10;
            while (attempts > 0)
            {
                attempts--;

                OperatorNode nodeAtFatherToCross = GetRandomCrossoverPoint(fatherChildList);
                OperatorNode nodeAtMotherToCross = GetRandomCrossoverPoint(motherList);

                if (MaxSize != 0 && fatherChild.Size - nodeAtFatherToCross.Size + nodeAtMotherToCross.Size > MaxSize)
                {
                    continue;
                }

                if (MaxDepth != 0 && fatherChild.GetDepthPosition(nodeAtFatherToCross) + nodeAtMotherToCross.Depth > MaxDepth)
                {
                    continue;
                }

                nodeAtFatherToCross.CopyFrom(nodeAtMotherToCross);
            }
        }

        public void VerifyCompactCodes()
        {
            foreach (Operator @operator in Operators)
            {
                if (@operator.CompactCode == null)
                {
                    throw new InvalidOperationException(string.Format("{0} must have a CompactCode!", @operator.Name));
                }
            }
        }

        internal Operator GetFunctionByToken(string token)
        {
            // Primary matches are most operators, including constants
            IEnumerable<Operator> matching = Operators.Where(func => func.MatchesToken(token, TokenPriority.Primary));
            Operator @operator = matching.SingleOrDefault();
            if (@operator == null)
            {
                // Secondary matches are things like ephemerals, which might match the same
                // things (and match stuff that would also be matched by constants).
                matching = Operators.Where(func => func.MatchesToken(token, TokenPriority.Secondary));
                @operator = matching.FirstOrDefault();
                if (@operator == null)
                {
                    throw new InvalidOperationException(
                        string.Format(
                            "GenomeDefinition doesn't contain Operator '{0}'",
                            token));
                }
            }

            return @operator.GetFromToken(token);
        }

        internal Operator GetFunctionByCompactToken(string token)
        {
            Operator @operator = Operators.SingleOrDefault(func => func.MatchesCompactToken(token));
            if (@operator == null)
            {
                throw new InvalidOperationException(
                    string.Format(
                        "GenomeDefinition doesn't contain Operator '{0}'",
                        token));
            }

            return @operator.GetFromToken(token);
        }

        private OperatorNode GenerateRandomOperatorNode(int currentDepth, int maxDepth)
        {
            if (currentDepth < maxDepth)
            {
                return GenerateRandomNonTerminalNode(currentDepth, maxDepth);
            }

            return GenerateRandomTerminalNode();
        }

        private OperatorNode GenerateRandomTerminalNode()
        {
            List<Operator> terminals = Terminals.Where(term => term.LimitUsageToPrexistingItems == false).ToList();
            Operator @operator = terminals.GetRandomItem();

            OperatorNode operatorNode = new OperatorNode();
            operatorNode.Operator = @operator;
            return operatorNode;
        }

        private OperatorNode GenerateRandomNonTerminalNode(int currentDepth, int maxDepth)
        {
            List<Operator> nonTerminals = NonTerminals.Where(term => term.LimitUsageToPrexistingItems == false).ToList();
            OperatorNode operatorNode = new OperatorNode();
            Operator @operator = nonTerminals.GetRandomItem();
            operatorNode.Operator = @operator;
            while (operatorNode.Children.Count() < @operator.Arity)
            {
                operatorNode.Children.Add(GenerateRandomOperatorNode(currentDepth + 1, maxDepth));
            }

            return operatorNode;
        }

        private void VerifyThatGenomeDefinitionIsComplete()
        {
            if (_verified == false)
            {
                if (Terminals.Any() == false)
                {
                    throw new InvalidOperationException("GenomeDefinition contains no terminals!");
                }

                if (NonTerminals.Any() == false)
                {
                    throw new InvalidOperationException("GenomeDefinition contains no non-terminals!");
                }
            }
            _verified = true;
        }

        private OperatorNode GetRandomCrossoverPoint(List<OperatorNode> fatherChildList)
        {
            // Use internal/external 10% of the time
            if (Population.Random.Next(10) == 1)
            {
                return fatherChildList.GetRandomItem();
            }

            // 90%, try to find an internal node and
            List<OperatorNode> internalNodes = fatherChildList.Where(node => node.Children.Count > 0).ToList();
            if (internalNodes.Count > 0)
            {
                return internalNodes.GetRandomItem();
            }

            return fatherChildList.GetRandomItem();
        }
    }
}
